"""
短信服务工具类
支持:
1. 阿里云短信服务
2. 腾讯云短信服务
"""

import json
import re
from dataclasses import dataclass
from enum import Enum
from logging import getLogger
from typing import Optional, Union

from alibabacloud_dysmsapi20170525 import models as dysmsapi_models
from alibabacloud_dysmsapi20170525.client import Client as AliyunClient
from alibabacloud_tea_openapi import models as aliyun_models
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.sms.v20210111 import models as tencent_models
from tencentcloud.sms.v20210111 import sms_client

from app.core.config import settings
from app.core.exceptions import APIException
from app.core.i18n import t

logger = getLogger(__name__)


class SMSProvider(str, Enum):
    """短信服务提供商"""

    ALIYUN = "aliyun"  # 阿里云
    TENCENT = "tencent"  # 腾讯云


class SMSError(Exception):
    """短信服务异常基类"""

    pass


class SMSProviderError(SMSError):
    """服务商API调用异常"""

    pass


class SMSConfigError(SMSError):
    """配置错误"""

    pass


class SMSValidationError(SMSError):
    """验证错误"""

    pass


@dataclass
class SMSConfig:
    """短信配置"""

    provider: SMSProvider
    region: str
    sign_name: str
    template_id: str
    access_key: str
    access_secret: str
    sdk_app_id: Optional[str] = None  # 仅腾讯云需要


class PhoneNumber:
    """电话号码处理类"""

    PHONE_REGEX = {
        "CN": re.compile(r"^(\+86|86)?1[3-9]\d{9}$"),  # 中国大陆
        "INTL": re.compile(r"^\+?[1-9]\d{1,14}$"),  # 国际
    }

    def __init__(self, number: str):
        self.original = number
        self.normalized = self._normalize(number)
        if not self._validate():
            raise SMSValidationError(t("error.sms.invalid_phone"))

    def _normalize(self, number: str) -> str:
        """标准化手机号"""
        # 去除所有空格
        number = "".join(number.split())

        # 处理中国大陆手机号
        if self.PHONE_REGEX["CN"].match(number):
            if number.startswith("+86"):
                return number[3:]
            elif number.startswith("86"):
                return number[2:]
            return number

        # 处理国际手机号
        if not number.startswith("+"):
            return f"+{number}"
        return number

    def _validate(self) -> bool:
        """验证手机号格式"""
        return bool(self.PHONE_REGEX["CN"].match(self.original) or self.PHONE_REGEX["INTL"].match(self.original))

    def for_provider(self, provider: SMSProvider) -> str:
        """获取服务商所需的手机号格式"""
        if provider == SMSProvider.ALIYUN:
            return self.normalized.lstrip("+")  # 阿里云不需要加号
        return self.normalized  # 腾讯云需要加号


class SMSHelper:
    """短信服务工具类"""

    def __init__(self, config: Optional[SMSConfig] = None):
        self.config = config or self._get_default_config()
        self._client: Optional[Union[AliyunClient, sms_client.SmsClient]] = None

    @property
    def client(self) -> Union[AliyunClient, sms_client.SmsClient]:
        """懒加载获取客户端"""
        if self._client is None:
            self._init_client()
        return self._client

    def _get_default_config(self) -> SMSConfig:
        """获取默认配置"""
        provider = SMSProvider(settings.SMS_PROVIDER)

        if provider == SMSProvider.ALIYUN:
            return SMSConfig(
                provider=provider,
                region=settings.REGION,
                sign_name=settings.SMS_SIGN_NAME,
                template_id=settings.SMS_TEMPLATE_ID,
                access_key=settings.ACCESS_KEY,
                access_secret=settings.ACCESS_SECRET,
            )
        else:
            return SMSConfig(
                provider=provider,
                region=settings.REGION,
                sign_name=settings.SMS_SIGN_NAME,
                template_id=settings.SMS_TEMPLATE_ID,
                access_key=settings.ACCESS_KEY,
                access_secret=settings.ACCESS_SECRET,
                sdk_app_id=settings.SDK_APP_ID,
            )

    def _init_client(self) -> None:
        """初始化短信客户端"""
        try:
            if self.config.provider == SMSProvider.ALIYUN:
                config = aliyun_models.Config(
                    access_key_id=self.config.access_key, access_key_secret=self.config.access_secret
                )
                config.endpoint = f"dysmsapi.{self.config.region}.aliyuncs.com"
                self._client = AliyunClient(config)
            else:
                cred = credential.Credential(self.config.access_key, self.config.access_secret)
                self._client = sms_client.SmsClient(cred, self.config.region)
        except Exception as e:
            logger.error(f"初始化短信客户端失败: {str(e)}")
            raise SMSConfigError(t("error.sms.init_failed"))

    async def send_verification_code(self, phone: str, code: str) -> None:
        """
        发送验证码短信
        :param phone: 手机号
        :param code: 验证码
        :raises:
            SMSValidationError: 手机号格式错误
            SMSProviderError: 服务商API调用失败
            SMSConfigError: 配置错误
        """
        try:
            # 验证并格式化手机号
            phone_number = PhoneNumber(phone)

            if self.config.provider == SMSProvider.ALIYUN:
                await self._send_aliyun_sms(phone_number, code)
            else:
                await self._send_tencent_sms(phone_number, code)

        except SMSError:
            raise
        except Exception as e:
            logger.error(f"发送短信时发生未知错误: {str(e)}")
            raise APIException(message=t("error.sms.send_failed"))

    async def _send_aliyun_sms(self, phone: PhoneNumber, code: str) -> None:
        """
        发送阿里云短信
        :param phone: 手机号对象
        :param code: 验证码
        """
        try:
            send_request = dysmsapi_models.SendSmsRequest(
                phone_numbers=phone.for_provider(SMSProvider.ALIYUN),
                sign_name=self.config.sign_name,
                template_code=self.config.template_id,
                template_param=json.dumps({"code": code}),
            )

            response = await self.client.send_sms_async(send_request)

            if response.body.code != "OK":
                logger.error(f"阿里云短信发送失败: {response.body.message}")
                raise SMSProviderError(t("error.sms.provider_failed"))

        except Exception as e:
            logger.error(f"阿里云短信发送异常: {str(e)}")
            if isinstance(e, SMSError):
                raise
            raise SMSProviderError(t("error.sms.provider_failed"))

    async def _send_tencent_sms(self, phone: PhoneNumber, code: str) -> None:
        """
        发送腾讯云短信
        :param phone: 手机号对象
        :param code: 验证码
        """
        try:
            req = tencent_models.SendSmsRequest()
            req.SmsSdkAppId = self.config.sdk_app_id
            req.SignName = self.config.sign_name
            req.TemplateId = self.config.template_id
            req.PhoneNumberSet = [phone.for_provider(SMSProvider.TENCENT)]
            req.TemplateParamSet = [code]

            response = self.client.SendSms(req)

            if response.SendStatusSet[0].Code != "Ok":
                logger.error(f"腾讯云短信发送失败: {response.SendStatusSet[0].Message}")
                raise SMSProviderError(t("error.sms.provider_failed"))

        except TencentCloudSDKException as e:
            logger.error(f"腾讯云短信发送异常: {str(e)}")
            raise SMSProviderError(t("error.sms.provider_failed"))
        except Exception as e:
            logger.error(f"腾讯云短信发送时发生未知错误: {str(e)}")
            if isinstance(e, SMSError):
                raise
            raise SMSProviderError(t("error.sms.provider_failed"))


# 单例模式
sms_helper = SMSHelper()
